﻿/*
Copyright (c) 2010 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using TeachMe.Application.ComboItems;
using TeachMe.Application.Configuration;
using TeachMe.Application.Data;
using TeachMe.Application.Events;
using TeachMe.Application.Exceptions;
using TeachMe.Application.Properties;

namespace TeachMe.Application.Forms
{
    public partial class DictionariesManagerForm : PseudoModalForm
    {
        private delegate void ModifyDictionaryDelegate(EditedDictionaryDescriptor descriptor);

        private delegate EditedDictionaryDescriptor PerformDelegate();

        // dictionaries are added into root object instantly so we can display entries & edit them
        // this means they are deleted from root object as well and in case user hit Cancel we must bring it back again (via _deletedDictionaries)
        private readonly List<EditedDictionaryDescriptor> _dictionaries = new List<EditedDictionaryDescriptor>();
        private readonly List<EditedDictionaryDescriptor> _deletedDictionaries = new List<EditedDictionaryDescriptor>();
        private readonly Root _root;

        public DictionariesManagerForm(Root root)
        {
            _root = root;
            _root.UserSettings.CheckOut();
            _root.MachineSettings.CheckOut();

            InitializeComponent();
            InitializeGrid();
            InitializePriorityChanger();

            dictionariesBindingSource.DataSource = _dictionaries;

            LoadDicts();

            WindowProportions.Lock(this, _root.MachineSettings);
            GridProportions.Lock(this, grid, _root.MachineSettings);
        }

        private int LastSelectedRowIndex
        {
            get { return GetSelectedRows().Max(x => x.Index); }
        }

        private void AddExistingDictionary()
        {
            if (Directory.Exists(this._root.MachineSettings.LastBrowseDirectory))
            {
                this.dlgAddDictionary.InitialDirectory = this._root.MachineSettings.LastBrowseDirectory;
            }

            if (this.dlgAddDictionary.ShowDialog() == DialogResult.OK)
            {
                using (this._root.MachineSettings.Edit())
                {
                    this._root.MachineSettings.LastBrowseDirectory = Path.GetDirectoryName(this.dlgAddDictionary.FileName);
                }

                foreach (string path in this.dlgAddDictionary.FileNames)
                {
                    string localPath = path;
                    if (_dictionaries.Select(x => x.Dictionary).Any(x => String.Compare(x.UserData.Path, localPath, true) == 0))
                    {
                        MessageBox.Show(String.Format(Resources.DictionaryAlreadyUsed, this.dlgAddDictionary.FileName), Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Stop);
                    }
                    else
                    {
                        AddExistingDictionary(path);
                    }
                }

                UpdateDictsToolbar();
            }
        }

        private void AddExistingDictionary(string path)
        {
            Dictionary dictionary;

            try
            {
                dictionary = _root.AddNewDictionary(path);
            }
            catch (DictionaryException exception)
            {
                Logging.Error(GetType(), exception, "Dictionary could not be added.");
                MessageBox.Show(Resources.DictionaryCreateError, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);

                return;
            }

            if (dictionary == null)
            {
                return;
            }

            dictionariesBindingSource.Add(EditedDictionaryDescriptor.CreateNew(dictionary));
        }

        private void AlterEntries(PerformDelegate performDelegate)
        {
            int column = grid.CurrentCell != null ? grid.CurrentCell.ColumnIndex : 0;
            EditedDictionaryDescriptor nextSelection = null;
            try
            {
                grid.SuspendLayout();
                nextSelection = performDelegate();
            }
            finally
            {
                if (nextSelection != null)
                {
                    Select(nextSelection, column);
                }
                grid.ResumeLayout();
            }
        }

        private bool AreDictsModified()
        {
            foreach (EditedDictionaryDescriptor descriptor in _dictionaries)
            {
                switch (descriptor.State)
                {
                    case EditedItemState.Normal:
                        if (descriptor.IsModified())
                        {
                            return true;
                        }
                        break;
                    case EditedItemState.New:
                        return true;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            return _deletedDictionaries.Count > 0;
        }

        private void CreateNewDict()
        {
            EntriesEditorForm entriesEditorForm = new EntriesEditorForm(_root, new List<Dictionary>(_dictionaries.Select(x => x.Dictionary)));

            entriesEditorForm.PseudoParent = this;
            entriesEditorForm.Show();
            entriesEditorForm.Closed += delegate
            {
                if (entriesEditorForm.CreatedDictionary != null)
                {
                    dictionariesBindingSource.Add(EditedDictionaryDescriptor.CreateNew(entriesEditorForm.CreatedDictionary));
                }

                UpdateDictsToolbar();
            };
        }

        private void EditSelectedDict()
        {
            EditedDictionaryDescriptor descriptor = GetSortedSelection().First(x => true);
            if (descriptor == null)
            {
                throw new InvalidOperationException();
            }

            EntriesEditorForm form = new EntriesEditorForm(_root, new List<Dictionary>(_dictionaries.Select(x => x.Dictionary)), descriptor.Dictionary);
            _root.FormsManager.Add(form);
            form.PseudoParent = this;
            form.Show();
            form.Closed += delegate
            {
                try
                {
                    UpdateDictsToolbar();
                }
                finally
                {
                    _root.FormsManager.Remove(form);
                }
            };
        }

        private Dictionary GetDictionary(string path)
        {
            return _dictionaries.Select(x => x.Dictionary).FirstOrDefault(dict => String.Compare(dict.UserData.Path, path, true) == 0);
        }

        private void GetSelectedDictsEnabledState(out bool disabled, out bool enabled)
        {
            enabled = false;
            disabled = false;

            foreach (EditedDictionaryDescriptor dictionary in GetSelection())
            {
                if (dictionary.Enabled)
                {
                    enabled = true;
                }
                else
                {
                    disabled = true;
                }

                if (enabled && disabled)
                {
                    break;
                }
            }
        }

        private IEnumerable<DataGridViewRow> GetSelectedRows()
        {
            return grid.SelectedCells.Cast<DataGridViewCell>().Select(x => grid.Rows[x.RowIndex]).Where(x => !x.IsNewRow).Distinct();
        }

        private IEnumerable<EditedDictionaryDescriptor> GetSelection()
        {
            return GetSelectedRows().Select(x => _dictionaries[x.Index]);
        }

        private int GetSelectionSize()
        {
            return GetSelectedRows().Count();
        }

        private IEnumerable<EditedDictionaryDescriptor> GetSortedSelection()
        {
            return GetSelectedRows().OrderBy(x => x.Index).Select(x => _dictionaries[x.Index]);
        }

        private void ChangePriorityForSelectedItems(Priority priority)
        {
            ModifySelectedDicts(delegate(EditedDictionaryDescriptor descriptor)
            {
                descriptor.Priority = priority;
            });
        }

        private void InitializeDirections()
        {
            foreach (var item in ComboBoxItemsStore.Directions)
            {
                colDirection.Items.Add(item);
            }
        }

        private void InitializeGrid()
        {
            // fixing drawing performance issues ??
            typeof(DataGridView).InvokeMember("DoubleBuffered", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, grid, new object[] { true });

            prioritiesBindingSource.DataSource = ComboBoxItemsStore.Priorities.Where(x => x.Item != Priority.VeryLow);

            InitializeDirections();

            grid.CellParsing += OnGridCellParsing;
        }

        private void InitializePriorityChanger()
        {
            cmiPriority.Text = Resources.ColumnPriority;

            cmiPriority.DropDownItems.Add(Resources.LowPriority, Resources.priority_low).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Low));
            cmiPriority.DropDownItems.Add(Resources.NormalPriority, Resources.priority_normal).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Normal));
            cmiPriority.DropDownItems.Add(Resources.HighPriority, Resources.priority_high).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.High));

            tsbPriority.DropDownItems.Add(Resources.LowPriority, Resources.priority_low).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Low));
            tsbPriority.DropDownItems.Add(Resources.NormalPriority, Resources.priority_normal).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Normal));
            tsbPriority.DropDownItems.Add(Resources.HighPriority, Resources.priority_high).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.High));
        }

        private bool IsCellReadOnly(int colIndex)
        {
            return grid.Columns[colIndex].ReadOnly;
        }

        private bool IsEditing()
        {
            return grid.IsCurrentCellInEditMode;
        }

        private void LoadDicts()
        {
            dictionariesBindingSource.Clear();

            _deletedDictionaries.Clear();

            AlterEntries(delegate
            {
                foreach (Dictionary dictionary in _root.Dictionaries)
                {
                    dictionariesBindingSource.Add(EditedDictionaryDescriptor.Create(dictionary));
                }

                return null;
            });
        }

        private void ModifySelectedDicts(ModifyDictionaryDelegate modifyDictionaryDelegate)
        {
            foreach (EditedDictionaryDescriptor descriptor in GetSelection())
            {
                modifyDictionaryDelegate(descriptor);
            }

            grid.Refresh();

            UpdateDictsToolbar();
        }

        private void RefreshSelectedDicts()
        {
            bool failed = false;

            foreach (EditedDictionaryDescriptor descriptor in GetSelection())
            {
                try
                {
                    _root.RefreshDictionary(descriptor.Dictionary);
                }
                catch (DictionaryException exception)
                {
                    Logging.Error(GetType(), exception, "Dictionary could not be refreshed.");

                    failed = true;
                }
            }

            if (failed)
            {
                MessageBox.Show(Resources.DictionaryRefreshError, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            grid.Refresh();
        }

        private void RemoveSelectedDicts()
        {
            if (GetSelectedRows().Count() == 0)
            {
                return;
            }

            List<EditedDictionaryDescriptor> deleteCompletely = new List<EditedDictionaryDescriptor>();
            EditedDictionaryDescriptor selection = null;

            int column = grid.CurrentCell != null ? grid.CurrentCell.ColumnIndex : 0;
            int row = LastSelectedRowIndex;

            if (++row < _dictionaries.Count)
            {
                selection = _dictionaries[row];
            }

            foreach (EditedDictionaryDescriptor descriptor in GetSelection())
            {
                if (descriptor.State == EditedItemState.Normal)
                {
                    _deletedDictionaries.Add(descriptor);
                }

                _root.RemoveDictionary(descriptor.Dictionary);

                deleteCompletely.Add(descriptor);
            }

            AlterEntries(delegate
            {
                foreach (EditedDictionaryDescriptor descriptor in deleteCompletely)
                {
                    dictionariesBindingSource.Remove(descriptor);
                }
                return null;
            });

            if (selection == null)
            {
                selection = _dictionaries.LastOrDefault();
            }

            if (selection != null)
            {
                Select(selection, column);
            }

            UpdateDictsToolbar();
        }

        private void RevertDicts()
        {
            foreach (EditedDictionaryDescriptor descriptor in _dictionaries)
            {
                if (descriptor.State == EditedItemState.New)
                {
                    _root.RemoveDictionary(descriptor.Dictionary);
                }
            }

            // this will add dicts back again ignoring current changes but preserving other related stuff like priorities
            foreach (EditedDictionaryDescriptor descriptor in _deletedDictionaries)
            {
                _root.AddNewDictionary(descriptor.Dictionary.UserData.Path);
            }
        }

        private void SaveDicts()
        {
            foreach (EditedDictionaryDescriptor descriptor in _dictionaries)
            {
                switch (descriptor.State)
                {
                    case EditedItemState.New:
                    case EditedItemState.Normal:
                        if (descriptor.IsModified())
                        {
                            descriptor.Dictionary.UpdateUserSettings(descriptor.Enabled, descriptor.Direction, descriptor.Priority);
                        }
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }

        private void Select(EditedDictionaryDescriptor descriptor, int column = 0)
        {
            if (descriptor != null)
            {
                int row = _dictionaries.IndexOf(descriptor);

                grid.CurrentCell = null;
                grid.CurrentCell = grid.Rows[row].Cells[column];
            }
        }

        private void SelectAll()
        {
            grid.SelectAll();
        }

        private void UpdateDictsToolbar()
        {
            int selectionSize = GetSelectionSize();
            tsbRemoveDict.Enabled = selectionSize > 0;
            tsbRefreshDict.Enabled = selectionSize > 0;
            tsbEditDict.Enabled = selectionSize == 1;
            tsbEnabled.Enabled = selectionSize > 0;
            tsbPriority.Enabled = selectionSize > 0;
        }

        #region Overrides

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (Utilities.Is(keyData, Keys.Control | Keys.A) && grid.Focused)
            {
                SelectAll();
            }

            if (Utilities.Is(keyData, Keys.Insert) && !IsEditing())
            {
                CreateNewDict();
            }

            if (Utilities.Is(keyData, Keys.Delete) && !IsEditing())
            {
                RemoveSelectedDicts();
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            if (keyData == Keys.Escape && (grid.IsCurrentCellInEditMode))
            {
                grid.CancelEdit();
                return true;
            }

            return base.ProcessDialogKey(keyData);
        }

        #endregion

        #region Event handlers

        private void OnAddDictButtonClick(object sender, EventArgs e)
        {
            AddExistingDictionary();
        }

        private void OnAddDictionaryUI(object sender, InputEventArgs<Dictionary> e)
        {
            dictionariesBindingSource.Add(EditedDictionaryDescriptor.CreateNew(e.Input));

            UpdateDictsToolbar();
        }

        private void OnAddOnlineDictsButtonClick(object sender, EventArgs e)
        {
            OnlineDictsManager manager = new OnlineDictsManager(_root, this);

            manager.GetLoadedDictionary += OnGetLoadedDictionary;
            manager.GetLoadedDictionaryId += OnGetLoadedDictionaryId;
            manager.IsDictionaryLoaded += OnIsDictionaryLoaded;
            manager.AddDictionaryUI += OnAddDictionaryUI;
            manager.RefreshDictionaryUI += OnRefreshDictionaryUI;

            manager.AddOnlineDicts();
        }

        private void OnCellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (GetSelectionSize() == 1 && this.IsCellReadOnly(e.ColumnIndex) && e.RowIndex >= 0 /*exclude header*/)
            {
                EditSelectedDict();
            }
        }

        private void OnContextMenuDictsRefreshClick(object sender, EventArgs e)
        {
            RefreshSelectedDicts();
        }

        private void OnContextMenuDictsRemoveClick(object sender, EventArgs e)
        {
            RemoveSelectedDicts();
        }

        private void OnContextMenuEditEntries(object sender, EventArgs e)
        {
            EditSelectedDict();
        }

        private void OnCreateDictButtonClick(object sender, EventArgs e)
        {
            CreateNewDict();
        }

        private void OnDictionaryContextMenuOpening(object sender, CancelEventArgs e)
        {
            int selectionSize = GetSelectionSize();
            cmiEdit.Enabled = selectionSize == 1;
            cmiRefresh.Enabled = selectionSize > 0;
            cmiRemove.Enabled = selectionSize > 0;
            cmiEnable.Enabled = selectionSize > 0;
            cmiDisable.Enabled = selectionSize > 0;
            cmiPriority.Enabled = selectionSize > 0;

            bool enabled, disabled;
            GetSelectedDictsEnabledState(out disabled, out enabled);

            if (enabled == disabled)
            {
                cmiEnable.Checked = false;
                cmiDisable.Checked = false;
            }
            else if (enabled)
            {
                cmiEnable.Checked = true;
                cmiDisable.Checked = false;
            }
            else
            {
                cmiEnable.Checked = false;
                cmiDisable.Checked = true;
            }
        }

        private void OnDisableSelection(object sender, EventArgs e)
        {
            ModifySelectedDicts(delegate(EditedDictionaryDescriptor descriptor)
            {
                descriptor.Enabled = false;
            });
        }

        private void OnEditSelectedDict(object sender, EventArgs e)
        {
            EditSelectedDict();
        }

        private void OnEnabledDisabledMenuOpening(object sender, EventArgs e)
        {
            bool disabled, enabled;

            GetSelectedDictsEnabledState(out disabled, out enabled);

            if (enabled == disabled)
            {
                miEnable.Checked = false;
                miDisable.Checked = false;
            }
            else if (enabled)
            {
                miEnable.Checked = true;
                miDisable.Checked = false;
            }
            else
            {
                miEnable.Checked = false;
                miDisable.Checked = true;
            }
        }

        private void OnEnableSelection(object sender, EventArgs e)
        {
            ModifySelectedDicts(delegate(EditedDictionaryDescriptor descriptor)
            {
                descriptor.Enabled = true;
            });
        }

        private void OnFormClosed(object sender, FormClosedEventArgs e)
        {
            _root.MachineSettings.CheckIn();
        }

        private void OnFormClosing(object sender, FormClosingEventArgs e)
        {
            if (DialogResult == DialogResult.Cancel)
            {
                if (AreDictsModified())
                {
                    RevertDicts();
                }

                _root.UserSettings.Revert();

                return;
            }

            if (AreDictsModified())
            {
                SaveDicts();
            }

            _root.UserSettings.CheckIn();
        }

        private void OnGetLoadedDictionary(object sender, InputOutputEventArgs<string, Dictionary> e)
        {
            e.Output = this.GetDictionary(e.Input);
        }

        private void OnGetLoadedDictionaryId(object sender, InputOutputEventArgs<string, Guid> e)
        {
            e.Output = this.GetDictionary(e.Input) != null ? this.GetDictionary(e.Input).Entries.Id : Guid.Empty;
        }

        private void OnGridCellParsing(object sender, DataGridViewCellParsingEventArgs e)
        {
            if (e.ColumnIndex == colPriority.Index)
            {
                e.Value = ComboBoxItemsStore.Priorities.First(x => x.ToString().Equals(e.Value));
                e.ParsingApplied = true;
            }
            else if (e.ColumnIndex == colDirection.Index)
            {
                e.Value = ComboBoxItemsStore.Directions.First(x => x.ToString().Equals(e.Value));
                e.ParsingApplied = true;
            }
        }

        private void OnIsDictionaryLoaded(object sender, InputOutputEventArgs<string, bool> e)
        {
            e.Output = this.GetDictionary(e.Input) != null;
        }

        private void OnRefreshDictionaryUI(object sender, InputEventArgs<Dictionary> e)
        {
            grid.Refresh();

            UpdateDictsToolbar();
        }

        private void OnRefreshDictsButtonClick(object sender, EventArgs e)
        {
            RefreshSelectedDicts();
        }

        private void OnRemoveDictsButtonClick(object sender, EventArgs e)
        {
            RemoveSelectedDicts();
        }

        #endregion
    }
}
